package com.gaojinqi.base.common.exception;

import com.alibaba.fastjson.JSONException;
import com.gaojinqi.base.common.constant.RespConst;
import com.gaojinqi.base.common.vo.IResult;
import com.gaojinqi.base.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import java.util.HashMap;
import java.util.Map;

import static com.gaojinqi.base.common.constant.RespConst.RespEnum;

/**
 * 全局的的异常拦截器（拦截所有的控制器）（带有@RequestMapping注解的方法上都会拦截）
 * 配置多个 ControllerAdvice 时，可根据 @Order 排序优先级，但只作用于类上，方法上加无效
 *
 * @author gaojinqi
 * @version 1.0
 * @since 2020年05月18日
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /***
     * 404处理
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResultVo<Object> notFountHandler(HttpServletRequest request, NoHandlerFoundException e) {
        log.warn("this is a NoHandlerFoundException at {},{}", System.currentTimeMillis(), e.getMessage());
        Map<String, Object> data = new HashMap<>();
        data.put("method", e.getHttpMethod());
        data.put("path", e.getRequestURL());
        //data.put("header", e.getHeaders());
        return ResultVo.fail(RespEnum.NOT_FOUND, data);
    }

    /**
     * 验证相关约束
     */
    @ExceptionHandler({
            BindException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class
    })
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public IResult<?> exceptionHandler(HttpServletRequest request, Exception exception) {
        log.warn("this is a global Exception at {},{}", System.currentTimeMillis(), exception.getMessage());
        exception.printStackTrace();
        BindingResult bindResult = null;
        StringBuilder sb = null;
        if (exception instanceof BindException) {
            bindResult = ((BindException) exception).getBindingResult();
        } else if (exception instanceof MethodArgumentNotValidException) {
            bindResult = ((MethodArgumentNotValidException) exception).getBindingResult();
        } else if (exception instanceof ConstraintViolationException) {
            sb = new StringBuilder();
            for (ConstraintViolation<?> constraintViolation : ((ConstraintViolationException) exception)
                    .getConstraintViolations()) {
                sb.append(constraintViolation.getMessage()).append(";");
            }
        }
        String msg;
        if (null != sb) {
            msg = sb.substring(0, sb.length() - 1);
            return ResultVo.fail(RespEnum.ILLEGAL.getCode(), msg);
        }
        if (bindResult != null && bindResult.hasErrors()) {
            msg = bindResult.getAllErrors().get(0).getDefaultMessage();
            if (msg == null) {
                msg = "请输入正确参数！";
            } else if (msg.contains("NumberFormatException")) {
                msg = "参数类型错误！";
            }
        } else {
            msg = "系统繁忙，请稍后重试...";
        }
        return ResultVo.fail(RespConst.RespEnum.ILLEGAL.getCode(), msg);
    }

    // 认证相关异常
    @ExceptionHandler(AuthException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public IResult<?> authException(HttpServletRequest request, AuthException e) {
        long time = System.currentTimeMillis();
        log.warn("this is a AuthException at {},{}", time, e.getMessage());
        return ResultVo.fail(e.getErrCode(), e.getMessage(), e.getData());
    }

    // 权限相关异常
    @ExceptionHandler(NoPermissionException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public IResult<?> authException(HttpServletRequest request, NoPermissionException e) {
        long time = System.currentTimeMillis();
        log.warn("this is a NoPermissionException at {},{}", time, e.getMessage());
        return ResultVo.fail(e.getErrCode(), e.getMessage(), e.getData());
    }

    // 业务异常
    @ExceptionHandler(BizException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public IResult<?> exceptionHandler(HttpServletRequest request, IException e) {
        long time = System.currentTimeMillis();
        log.warn("this is a BizException at {},{}", time, e.getMessage());
        return ResultVo.fail(e.getErrCode(), e.getMessage(), e.getData());
    }

    // 业务运行时异常
    @ExceptionHandler(BizRuntimeException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public IResult<?> runtimeExceptionHandler(HttpServletRequest request, IException e) {
        long time = System.currentTimeMillis();
        log.error("this is a BizRuntimeException at {},{}", time, e.getMessage());
        return ResultVo.fail(e.getErrCode(), e.getMessage(), e.getData());
    }

    // 所有异常，当上面没有对应匹配的子异常时会执行该方法
    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public IResult<?> excepitonHandler(HttpServletRequest request, Exception e) {
        long time = System.currentTimeMillis();
        if (e instanceof IException) {
            return exceptionHandler(request, (IException) e);
        }

        if (e instanceof RuntimeException) {
            return runtimeExceptionHandler(request, (RuntimeException) e);
        }
        log.error("{}, this is a global Exception,", time, e);
        return ResultVo.fail(RespEnum.UNKNOW.getCode(), "系统繁忙，请稍后再试");
    }

    public IResult<?> runtimeExceptionHandler(HttpServletRequest request, RuntimeException e) {

        // json转换实体类失败时异常处理
        if (e instanceof JSONException) {
            e.printStackTrace();
            return ResultVo.fail(RespEnum.ILLEGAL.getCode(), "请输入正确参数");
        }
        // 空值处理
        if (e instanceof NullPointerException) {
            e.printStackTrace();
            return ResultVo.fail(RespEnum.NULL.getCode(), "请输入正确参数");
        }

        if (e instanceof BizRuntimeException) {
            e.printStackTrace();
            BizRuntimeException bizRuntimeException = (BizRuntimeException) e;
            return ResultVo.fail(bizRuntimeException.getErrCode(), bizRuntimeException.getMessage());
        }

        long time = System.currentTimeMillis();
        log.error("{}, this is a global RuntimeException,", time, e);
        return ResultVo.fail(RespEnum.UNKNOW.getCode(), "系统繁忙，请稍后再试\"");
    }

}
